package com.manda.smart.hub.expression.extend;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.utils.Env;
import com.manda.smart.hub.expression.definition.FunctionMeta;
import com.manda.smart.hub.expression.definition.InvokeSession;
import com.manda.smart.hub.expression.definition.InvokeSessionHolder;
import com.manda.smart.hub.expression.definition.MethodMeta;
import com.manda.smart.hub.expression.plugin.*;
import lombok.Getter;

import java.lang.reflect.Array;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * 增强型函数
 * 基于可变参数函数进行增强
 *
 * @author hongda.li
 */
@SuppressWarnings("ReassignedVariable")
@Getter
public class EnhancedFunction extends AbstractVariadicFunction {

    private final FunctionMeta functionMeta;

    public EnhancedFunction(FunctionMeta functionMeta) {
        this.functionMeta = functionMeta;
    }

    @Override
    public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
        InvokeSession session = new InvokeSession((Env) env, args, this.functionMeta);

        try {
            // 绑定会话
            InvokeSessionHolder.setSession(session);

            // 绑定参数
            this.bindParams(session);

            // 执行前置回调插件
            this.choosePlugin(BeginPlugin.class, plugin -> plugin.onBegin(session));

            // 正常来说执行结果还没有被初始化，这里应该为空
            // 但如果不为空，说明前置插件已经设置了本次调用返回值
            // 那么就直接将前置插件的返回值作为最终结果
            if (session.getResult() != null) {
                // 如果前置插件设置了返回值，则会被视为执行成功
                // 因此同样会执行成功回调插件
                this.choosePlugin(SuccessPlugin.class, plugin -> plugin.onSuccess(session));

                // 统一包装返回值
                return FunctionUtils.wrapReturn(session.getResult());
            }

            Object result = session.getInvocation().invoke();

            // 设置当前调用的返回值
            session.setResult(result);

            // 执行成功回调插件
            this.choosePlugin(SuccessPlugin.class, plugin -> plugin.onSuccess(session));

            // 统一包装返回值
            // 这里的返回值取的是调用会话中的返回值
            // 也就意味着执行成功回调插件可以改变返回值
            return FunctionUtils.wrapReturn(session.getResult());
        } catch (Exception e) {
            // 设置当前调用的异常
            session.setException(e);

            // 设置失败回调插件
            this.choosePlugin(FailurePlugin.class, plugin -> plugin.onFailure(session));

            // 正常来说当前调用异常一定不为空
            // 但是如果为空说明失败回调插件清空了当前调用异常
            // 那么就直接返回调用会话中的返回值
            Optional.ofNullable(session.getException()).ifPresent(error -> {
                // 如果调用异常不为空，则将调用异常统一包装为 InvokeException 异常
                // 这样做是为了更好的区分整个脚本执行周期中的异常来源
                if (error instanceof InvokeException exception) {
                    throw exception;
                } else {
                    throw new InvokeException(error);
                }
            });

            // 返回并包装调用会话中的返回值
            return FunctionUtils.wrapReturn(session.getResult());
        } finally {
            try {
                // 执行最终回调插件
                this.choosePlugin(FinallyPlugin.class, plugin -> plugin.onFinally(session));
            } finally {
                // 清除会话
                InvokeSessionHolder.clear();
            }
        }
    }

    @Override
    public String getName() {
        return functionMeta.getName();
    }

    private <P extends Plugin> void choosePlugin(Class<P> type, Consumer<P> consumer) {
        List<Plugin> pluginList = functionMeta.getPluginList();
        if (CollUtil.isEmpty(pluginList)) {
            return;
        }
        pluginList.stream()
                .filter(plugin -> type.isAssignableFrom(plugin.getClass()))
                .map(type::cast)
                .forEach(consumer);
    }

    private void bindParams(InvokeSession session) {
        MethodMeta methodMeta = session.getFunctionMeta().getMethodMeta();

        if (methodMeta.method().getParameterCount() == 0) {
            session.setInvocation(methodMeta.toInvocation(null));
            return;
        }

        Env env = session.getEnv();

        AviatorObject[] actualArgs = session.getObjects();
        Parameter[] expectArgs = methodMeta.method().getParameters();

        Object[] args = new Object[expectArgs.length];

        for (int i = 0; i < expectArgs.length; i++) {
            args[i] = this.bindParams(i, env, expectArgs[i], actualArgs);
        }

        session.setInvocation(methodMeta.toInvocation(args));
    }

    private Object bindParams(int index, Env env, Parameter parameter, AviatorObject[] actualArgs) {
        Class<?> type = parameter.getType();
        if (index > actualArgs.length - 1) {
            return null;
        }
        if (AviatorObject.class.isAssignableFrom(type)) {
            return actualArgs[index];
        }
        if (type.isArray()) {
            Class<?> elementType = type.getComponentType();
            Object array = Array.newInstance(elementType, actualArgs.length - index);
            for (int i = index; i < actualArgs.length; i++) {
                Array.set(array, i - index, AviatorObject.class.isAssignableFrom(elementType)
                        ? actualArgs[i]
                        : Convert.convert(elementType, actualArgs[i].getValue(env)));
            }
            return array;
        }
        return Convert.convert(type, actualArgs[index].getValue(env));
    }
}
